package kjdss.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.sound.sampled.SourceDataLine;

import kjdss.KJDigitalSignalProcessor;
import kjdss.KJDigitalSignalSynchronizer;
import kjdss.KJFFT;

/**
 * @author Kris Fudalewski
 *
 * Using the KJDigitalSignalProcessor package, this component can display a realtime
 * scope, spectrum analyser, or volume meter.
 */
public class KJScopeAndSpectrumAnalyzer extends Component implements KJDigitalSignalProcessor {

	public static final BandDistribution BAND_DISTRIBUTION_LINEAR = new LinearBandDistribution();
	public static final BandDistribution BAND_DISTRIBUTION_LOG    = new LogBandDistribution( 4, 20.0 );
	
	public static final int   DISPLAY_MODE_SCOPE             = 0;
	public static final int   DISPLAY_MODE_SPECTRUM_ANALYSER = 1;
	public static final int   DISPLAY_MODE_VU_METER          = 2;
	
	public static final int   DEFAULT_WIDTH  = 256;
	public static final int   DEFAULT_HEIGHT = 128;

	public static final int   DEFAULT_SCOPE_DETAIL_LEVEL = 1;
	
	public static final int              DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT        = 20;
	public static final BandDistribution DEFAULT_SPECTRUM_ANALYSER_BAND_DISTRIBUTION = BAND_DISTRIBUTION_LOG;
	public static final float            DEFAULT_SPECTRUM_ANALYSER_DECAY             = 0.03f;
	public static final int              DEFAULT_SPECTRUM_ANALYSER_FFT_SAMPLE_SIZE   = 1024;
	public static final float            DEFAULT_SPECTRUM_ANALYSER_GAIN              = 3.0f;

	public static final Color DEFAULT_BACKGROUND_COLOR = new Color( 255, 255, 255);	
	public static final Color DEFAULT_SCOPE_COLOR      = new Color( 130, 192, 254); 
	public static final float DEFAULT_VU_METER_DECAY   = 0.03f;
	
	protected Image  bi;

	protected int displayMode = DISPLAY_MODE_SPECTRUM_ANALYSER;
	
	protected Color   scopeColor             = DEFAULT_SCOPE_COLOR; 
	protected Color[] spectrumAnalyserColors = getDefaultSpectrumAnalyserColors();
	
	protected int width;
	protected int height;
	protected int height_2;
	
	// -- Scope variables.
	protected int     scopeDetailLevel = DEFAULT_SCOPE_DETAIL_LEVEL;
	
	// -- Spectrum analyser varibles.
	protected float            saColorScale;   
	protected int              saFFTSampleSize;  
	protected float            saDecay = DEFAULT_SPECTRUM_ANALYSER_DECAY; 
	protected float            saGain = DEFAULT_SPECTRUM_ANALYSER_GAIN;

	protected int              saBands;   
	protected BandDistribution saBandDistribution = DEFAULT_SPECTRUM_ANALYSER_BAND_DISTRIBUTION;
	protected int[]            sabdTable;
	protected float            saBandWidth;
	
	protected KJFFT   fft; 
	protected float[] old_FFT;
	
	// -- Tabled log for performance.
	protected double[] logTable;
	
	// -- VU Meter
	protected float oldLeft;
	protected float oldRight;

	protected float vuDecay = DEFAULT_VU_METER_DECAY; 
	protected float vuColorScale;   
	
	// -- FPS calulations.
	private long    lfu = 0;
	private	int     fc  = 0;
	private int     fps;
	private boolean showFPS = false;
	
	public KJScopeAndSpectrumAnalyzer() {
		initialize();
	}
	
	/**
	 * Computes a color scale value for both the spectrum analyzers and volume meter bars.
	 * This ensures the colors are spread uniformly across the width or height of the respective bars.
	 */
	protected void computeColorScale() {
		saColorScale = ( (float)spectrumAnalyserColors.length / height ) * 2.0f;
		vuColorScale = ( (float)spectrumAnalyserColors.length / ( width - 32 ) ) * 2.0f;
	}

	/**
	 * Computes and stores a band distribution table for the spectrum analyzer. This is performed using
	 * the specified band distribution instance. See setSpectrumAnalyzerBandDistribution().
	 */
	protected void computeBandDistributionTable() {
		
		if ( saBands > 0 && saFFTSampleSize > 0 ) {
			sabdTable = saBandDistribution.create( saBands, saFFTSampleSize );
			saBands = sabdTable.length;
		}
		
	}
	
	/**
	 * @see drawScope
	 */
	protected void drawScope( Graphics pGrp, float[] pLeft, float[] pRight, float pFrrh ) {
		drawScope( pGrp, stereoMerge( pLeft, pRight ), pFrrh );
	}
	
	/**
	 * Draws a scope of the audio data across the entire width and height of this component.
	 * 
	 * @param pGrp AWT Graphics context.
	 * @param pSample Stereo merged audio data.
	 * @param pFrrh Frame rate ratio hint.
	 */
	protected void drawScope( Graphics pGrp, float[] pSample, float pFrrh ) {
		
		pGrp.setColor( scopeColor );
    	
    	int wLas = (int)( pSample[ 0 ] * (float)height_2 ) + height_2;
    	
       	for ( int a = scopeDetailLevel, c = 0; c < width && a < pSample.length; a += scopeDetailLevel, c += scopeDetailLevel ) {
    		
    		int wAs = (int)( pSample[ a ] * (float)height_2 ) + height_2;
    		
    		pGrp.drawLine( 
    		    c, 
				wLas, 
				c, 
				wLas );
    		
//    		pGrp.drawLine( 
//        		    c, 
//    				wLas, 
//    				c + scopeDetailLevel, 
//    				wAs );
    		
    		wLas = wAs;
    		
    	}
	       	
	}

	/**
	 * @see drawSpectrumAnalyzer
	 */
	protected void drawSpectrumAnalyser( Graphics pGrp, float[] pLeft, float[] pRight, float pFrrh ) {
		drawSpectrumAnalyser( pGrp, stereoMerge( pLeft, pRight ), pFrrh );
	}
	
	/**
	 * Draws a spectrum analyzer across the entire width and height if this component.
	 * 
	 * @param pGrp AWT Graphics context.
	 * @param pSample Stereo merged audio data.
	 * @param pFrrh Frame rate ratio hint.
	 */
	protected void drawSpectrumAnalyser( Graphics pGrp, float[] pSample, float pFrrh ) {
		
    	float c = 0;
    //	float wBw = ( (float)width / (float)saBands ); 
    	float wSadfrr = ( saDecay * pFrrh );
		
    	float[] wFFT = fft.calculate( pSample );
    	
    	int b, bd, i, li = 0;
		float fs, m;
		
		// -- Group up available bands using band distribution table.
    	for ( bd = 0; bd < saBands; bd++ ) {

    		// -- Get band distribution entry.
    		i = sabdTable[ bd ];

    		fs = 0;
    		m = 0;
    		
    		// -- Find loudest band in group. (Group is from 'li' to 'i')
    		for( b = li; b < i; b++ ) {
    			
    			float lf = wFFT[ b ];
    			
    			if ( lf > m ) {
    				m = lf;
    			}
    			
//    			System.out.println( "i: " + ( b ) );
    		}

    		li = i;
    		
    		// -- Calculate gain using log, then static gain.
    		fs = ( m * (float)( logTable[ li ] ) ) * saGain;
    		
    		// -- Limit over-saturation.
    		if ( fs > 1.0f ) { 
    			fs = 1.0f; 
    		}
    		
    		// -- Compute decay.
    		if ( fs >= ( old_FFT[ bd ] - wSadfrr ) ) {
    			
    			old_FFT[ bd ] = fs;
    			
    		} else {
    			
    			old_FFT[ bd ] -= wSadfrr;
    			
    			if ( old_FFT[ bd ] < 0 ) {
    				old_FFT[ bd ] = 0;
    			}
    			
    			fs = old_FFT[ bd ];
    			
    		}

    		// -- Draw band.
    		drawSpectrumAnalyserBand( 
    			pGrp, 
				(int)c, 
				height, 
				(int)saBandWidth - 1, 
				(int)( fs * height ) );
    		
    		c += saBandWidth;
    		
    	}
    	
//		System.out.println();
	}
	
	/**
	 * @param pGrp
	 * @param pLeft
	 * @param pRight
	 * @param pFrrh
	 */
	protected void drawVolumeMeter( Graphics pGrp, float[] pLeft, float[] pRight, float pFrrh ) {
	
		float wLeft  = 0.0f;
		float wRight = 0.0f;
		
    	float wSadfrr = ( vuDecay * pFrrh );
		
		for( int a = 0; a < pLeft.length; a++ ) {
			
			wLeft  += Math.abs( pLeft[ a ] );
			wRight += Math.abs( pRight[ a ] );
			
		}
		
		wLeft  = ( ( wLeft * 2.0f ) / (float)pLeft.length );
		wRight = ( ( wRight * 2.0f ) / (float)pRight.length );
		
		if ( wLeft > 1.0f ) {
			wLeft = 1.0f;
		}
		
		if ( wRight > 1.0f ) {
			wRight = 1.0f;
		}
		
//		vuAverage += ( ( wLeft + wRight ) / 2.0f ); 
//		vuSamples++;
//		
//		if ( vuSamples > 128 ) {
//			vuSamples /= 2.0f;
//			vuAverage /= 2.0f;
//		}
		
		if ( wLeft >= ( oldLeft - wSadfrr ) ) {
			oldLeft = wLeft;
		} else {

			oldLeft -= wSadfrr;
			
			if ( oldLeft < 0 ) {
				oldLeft = 0;
			}
			
		}
		
		if ( wRight >= ( oldRight - wSadfrr ) ) {
			oldRight = wRight;
		} else {
			
			oldRight -= wSadfrr;
			
			if ( oldRight < 0 ) {
				oldRight = 0;
			}
			
		}
		
		int wHeight = ( height >> 1 ) - 24;
		
		drawVolumeMeterBar( pGrp, 16, 16,           (int)( oldLeft  * (float)( width - 32 ) ), wHeight );
//		drawVolumeMeterBar( pGrp, 16, wHeight + 22, (int)( ( vuAverage / vuSamples ) * (float)( width - 32 ) ), 4 );
		drawVolumeMeterBar( pGrp, 16, wHeight + 32, (int)( oldRight * (float)( width - 32 ) ), wHeight );

		
		
//		pGrp.fillRect( 16, 16, (int)( oldLeft  * (float)( width - 32 ) ), wHeight );
//		pGrp.fillRect( 16, 64, (int)( oldRight * (float)( width - 32 ) ), wHeight );
		
	}
	
	/**
	 * Draws a single spectrum analyzer band on this component and the specified coordinates.
	 * 
	 * @param pGraphics AWT Graphics context 
	 * @param pX The X position to draw.
	 * @param pY The Y position to draw.
	 * @param pWidth The width of the band to draw.
	 * @param pHeight The height of the band to draw.
	 */
	protected void drawSpectrumAnalyserBand( Graphics pGraphics, int pX, int pY, int pWidth, int pHeight ) {
		
		float c = 0;
		
		for( int a = pY; a >= pY - pHeight; a -= 2 ) {
			
			c += saColorScale;
			
			if ( c < 256.0f ) {
				pGraphics.setColor( spectrumAnalyserColors[ (int)c ] );
			}
			
			pGraphics.fillRect( 
				pX, 
				a, 
				pWidth, 
				1 );
			
		}
		
	}

	/**
	 * Draws a volume meter bar on this component at the specified coordinates.
	 * 
	 * @param pGraphics AWT Graphics context 
	 * @param pX The X position to draw.
	 * @param pY The Y position to draw.
	 * @param pWidth The width of the bar to draw.
	 * @param pHeight The height of the bar to draw.
	 */
	protected void drawVolumeMeterBar( Graphics pGraphics, int pX, int pY, int pWidth, int pHeight ) {
		
		float c = 0;
		
		for( int a = pX; a <= pX + pWidth; a += 2 ) {
			
			c += vuColorScale;
			
			if ( c < 256.0f ) {
				pGraphics.setColor( spectrumAnalyserColors[ (int)c ] );
			}
			
			pGraphics.fillRect( 
				a, 
				pY, 
				1, 
				pHeight );
			
		}
		
	}
	
	
	/**
	 * Returns the double buffer used to draw off-screen.
	 * 
	 * @return Image representing an off-screen buffer.
	 */
	protected Image getDoubleBuffer() {
		
		// -- If the buffer is not created OR the size has changed, synch, double check.
		if ( bi == null || ( bi.getWidth( null ) != getSize().width || bi.getHeight( null ) != getSize().height ) ) {
			
			synchronized( getTreeLock() ) {
			
				// -- Check again.
				if ( bi == null || ( bi.getWidth( null ) != getSize().width || bi.getHeight( null ) != getSize().height ) ) {
				
					width    = getSize().width;
					height   = getSize().height;
					height_2 = height >> 1;
					
					saBandWidth = (float)width / (float)saBands;
			
					computeColorScale();
					
					bi = getGraphicsConfiguration().createCompatibleVolatileImage( 
						width,
						height );
					
				}
			
			}
		
		}
		
		return bi;
	
	}
	
	/**
	 * Creates a color array containing the default color spread of the spectrum analyzer.
	 * 
	 * @return Default color array for the spectrum analyzer.
	 */
	public static Color[] getDefaultSpectrumAnalyserColors() {
		
		
		Color[] wColors = new Color[ 256 ];
//										128
		for( int a = 0; a < 192; a++ ) {
			wColors[ a ] = new Color( 130, ( a >> 2 ) + 192, 254 );
		}
		
//		for( int a = 0; a < 64; a++ ) {
//			wColors[ a + 128 ] = new Color( a << 2, 255, 254 );
//		}

		for( int a = 0; a < 64; a++ ) {
			wColors[ a + 192 ] = new Color( 255, 255 - ( a << 2 ), 0 );
		}
		
		return wColors;
		
	}
	
	/**
	 * @return Returns the current display mode, DISPLAY_MODE_SCOPE or DISPLAY_MODE_SPECTRUM_ANALYSER.
	 */
	public int getDisplayMode() {
		return displayMode;
	}
	
	/**
	 * @return Returns the current number of bands displayed by the spectrum analyser.  
	 */
	public int getSpectrumAnalyserBandCount() {
		return saBands;
	}
	
	/**
	 * @return Returns the current band distribution type for the spectrum analyser.  
	 */
	public BandDistribution getSpectrumAnalyserBandDistribution() {
		return saBandDistribution;
	}
	
	/**
	 * @return Returns the decay rate of the spectrum analyser's bands. 
	 */
	public float getSpectrumAnalyserDecay() {
		return saDecay;
	}
	
	/**
	 * @return Returns the gain applied to the spectrum analyser's bands. 
	 */
	public float getSpectrumAnalyserGain() {
		return saGain;
	}
	
	/**
	 * @return Returns the color the scope is rendered in.
	 */
	public Color getScopeColor() {
		return scopeColor;
	}
	
	/**
	 * @return Returns the detail level of the scope.
	 */
	public int getScopeDetailLevel() {
		return scopeDetailLevel;
	}
	
	/**
	 * @return Returns the color scale used to render the spectrum analyser bars.
	 */
	public Color[] getSpectrumAnalyserColors() {
		return spectrumAnalyserColors;
	}
	
	protected void initialize() {
		
		setSize( DEFAULT_WIDTH, DEFAULT_HEIGHT );

		setBackground( DEFAULT_BACKGROUND_COLOR );
		
		prepareDisplayToggleListener();

		setSpectrumAnalyserBandCount( DEFAULT_SPECTRUM_ANALYSER_BAND_COUNT );

	}
	
	/**
	 * @see kj.dss.KJDigitalSignalProcessor#initialize( int, SourceDataLine )
	 */
	public void initialize( int pSampleSize, SourceDataLine pSourceDataLine ) {

		setSpectrumAnalyserFFTSampleSize( pSampleSize );
		
	}
	
	/**
	 * @return Returns 'true' if "Frames Per Second" are being calculated and displayed.  
	 */
	public boolean isShowingFPS() {
		return showFPS;
	}
	
	public void paint( Graphics pGraphics ) {
		pGraphics.drawImage( getDoubleBuffer(), 0, 0, null );
	}
	
	protected void prepareDisplayToggleListener() {
		
		setCursor( Cursor.getPredefinedCursor( Cursor.HAND_CURSOR ) );
		
		addMouseListener(
			new MouseAdapter() {
				public void mouseClicked( MouseEvent pEvent ) {
					
					if ( pEvent.getButton() == MouseEvent.BUTTON1 ) {
					
						if ( displayMode + 1 > 2 ) {
							displayMode = 0;
						} else { 
							displayMode++;
						}
						
					}
					
				}
			}
	    );
		
	}
	
	/**
	 * @see kj.dss.KJDigitalSignalProcessor#process( KJDigitalSignalSynchronizer.Context )
	 */
	public void process( KJDigitalSignalSynchronizer.Context pDssContext ) {

		float[][] wChannels = pDssContext.getDataNormalized();
		
		process( wChannels[ 0 ], wChannels[ 1 ], pDssContext.getFrameRatioHint() );
		
	}
	
	/**
	 * Old KJ DSP interface method.  
	 * 
	 * @param pLeft Left channel data from the DSS.
	 * @param pRight Right channel date from the DSS.
	 * @param pFrameRateRatioHint Frame rate ratio hint from the DSS.
	 */
	protected void process( float[] pLeft, float[] pRight, float pFrameRateRatioHint ) {
    	
		Image wDb = getDoubleBuffer();
    	Graphics wGrp = wDb.getGraphics(); 

    	wGrp.setColor( getBackground() );
    	wGrp.fillRect( 0, 0, getSize().width, getSize().height );
    	
    	switch( displayMode ) {
    		
    		case DISPLAY_MODE_SCOPE:
    			drawScope( wGrp, pLeft, pRight, pFrameRateRatioHint );
    			break;
    			
    		case DISPLAY_MODE_SPECTRUM_ANALYSER:
    			drawSpectrumAnalyser( wGrp, pLeft, pRight, pFrameRateRatioHint );
    			break;

    		case DISPLAY_MODE_VU_METER:
    			drawVolumeMeter( wGrp, pLeft, pRight, pFrameRateRatioHint );
    			break;
    			
    	}
    	
    	// -- Show FPS if necessary.
    	if ( showFPS ) {
    	
	    	// -- Calculate FPS.
			if ( System.currentTimeMillis() >= lfu + 1000 ) {
				
				lfu = System.currentTimeMillis();
				fps = fc;
				fc = 0;
				
			}
	
			fc++;
	    	
	    	wGrp.setColor( Color.yellow );
	    	wGrp.drawString( "FPS: " + fps + " (FRRH: " + pFrameRateRatioHint + ")", 0, height - 1 );
	    	
    	}
    	
		getGraphics().drawImage( wDb, 0, 0, null );
    	
    }
	
	/**
	 * Sets the current display mode.
	 * 
	 * @param pMode Must be either DISPLAY_MODE_SCOPE or DISPLAY_MODE_SPECTRUM_ANALYSER.
	 */
	public synchronized void setDisplayMode( int pMode ) {
		displayMode = pMode;
	}
	
	/**
	 * Sets the color of the scope.
	 * 
	 * @param pColor 
	 */
	public synchronized void setScopeColor( Color pColor ) {
		scopeColor = pColor;
	}

	/**
	 * Sets the detail level of the scope.
	 * 
	 * @param pDetailLevel 
	 */
	public synchronized void setScopeColor( int pDetailLevel ) {
		scopeDetailLevel = pDetailLevel;
	}
	
	/**
	 * When 'true' is passed as a parameter, will overlay the "Frames Per Seconds"
	 * achieved by the component.
	 * 
	 * @param pState
	 */
	public synchronized void setShowFPS( boolean pState ) {
		showFPS = pState;
	}
	
	/**
	 * Sets the numbers of bands rendered by the spectrum analyser. 
	 * 
	 * @param pCount Cannot be more than half the "FFT sample size".
	 */
	public synchronized void setSpectrumAnalyserBandCount( int pCount ) {
		
		saBands = pCount;
		
		computeBandDistributionTable();
		
	}
	
	/**
	 * Sets the band distribution type for the spectrum analyser. Band distribution refers to 
	 * how all bands in the spectrum will be grouped together into a smaller number of bands.
	 * There are two available type by default: BAND_DISTRIBUTION_LINEAR, BAND_DISTRIBUTION_LOG
	 * but custom band distributions can be create using the BandDistribution inner inteferace.
	 * 
	 * @param pBandDistribution A band distribution type.
	 */
	public synchronized void setSpectrumAnalyserBandDistribution( BandDistribution pBandDistribution ) {
		
		saBandDistribution = pBandDistribution;
		
		computeBandDistributionTable();
		
	}
	
	/**
	 * Sets the spectrum analyser band decay rate.
	 * 
	 * @param pDecay Must be a number between 0.0 and 1.0 exclusive.
	 */
	public synchronized void setSpectrumAnalyserDecay( float pDecay ) {
		saDecay = pDecay;
	}
	
	/**
	 * Sets the spectrum analyser band gain.
	 * 
	 * @param pGain This is a multiplier value, default is 2.0
	 */
	public synchronized void setSpectrumAnalyserGain( float pGain ) {
		saGain = pGain;
	}
	
	/**
	 * Sets the spectrum analyser color scale.
	 * 
	 * @param pColors Any amount of colors may be used. Must not be null.
	 */
	public synchronized void setSpectrumAnalyserColors( Color[] pColors ) {
		spectrumAnalyserColors = pColors;
		computeColorScale();
	}
	
	/**
	 * Sets the FFT sample size to be just for calculating the spectrum analyser 
	 * values. The default is 512. 
	 * 
	 * @param pSize Cannot be more than the size of the sample provided by the DSP.  
	 */
	public synchronized void setSpectrumAnalyserFFTSampleSize( int pSize ) {
		
		saFFTSampleSize = pSize;
		
		fft = new KJFFT( saFFTSampleSize );
		old_FFT = new float[ saBands ]; 
		
		logTable = new double[ pSize ];
		
		for( int a = 0; a < pSize; a++ ) {
			logTable[ a ]   = Math.log( a + 2 );
		}
		
		computeBandDistributionTable();
		
	}
	
	/**
	 * Merges two audio channels into one.
	 * 
	 * @param pLeft Left channel data.
	 * @param pRight Right channel data.
	 * 
	 * @return Merged results of the supplied left and right channel data.
	 */
	protected float[] stereoMerge( float[] pLeft, float[] pRight ) {
		
		for( int a = 0; a < pLeft.length; a++ ) {
			pLeft[ a ] = ( pLeft[ a ] + pRight[ a ] ) / 2.0f;
		}
		
		return pLeft;
		
	}
	
	public void update( Graphics pGraphics ) {
		// -- Prevent AWT from clearing background.
		paint( pGraphics );
	}
	
	/**
	 * Interface for band distribution types. Band distribution refers to combining band data
	 * into groups therefore reducing the number of visible bands. For example, a traditional 
	 * 10 band spectrum analyzer contains only 10 visible frequency bands sampled from a 
	 * potentially more than hundreds or more frequency bands. In order to distribute the bands
	 * into only 10, several different distributions can be typically be used such as log or 
	 * simply linear distribution.  
	 * 
	 * #see KJScopeSpectrumAnalyzer$LinearBandDistribution
	 * #see KJScopeSpectrumAnalyzer$LogBandDistribution
	 * 
	 * @author cwnl-kfudalew
	 */
	public static interface BandDistribution {
		
		/**
		 * @param pBandCount The desired number of visible bands.
		 * @param pSampleSize The total number of computed bands
		 * 
		 * @return A band distribution table.
		 */
		int[] create( int pBandCount, int pSampleSize );
		
	}
	
	/**
	 * Linear based band distribution class.
	 * 
	 * @author cwnl-kfudalew
	 */
	public static class LinearBandDistribution implements BandDistribution {

		public int[] create( int pBandCount, int pFFTSampleSize ) {

			// -- We actually only use half of the available data because the higher bands are 
			//    not audible by humans.
			int wHss = (int)pFFTSampleSize >> 1;
			
			int r = (int)( (double)wHss / (double)pBandCount );
			
			int[] wSabdTable = new int[ pBandCount ];
			
			int wBand = 0;
			
			for( double a = r; a < wHss && wBand < pBandCount; a += r ) {
				wSabdTable[ wBand++ ] = (int)a;
			}
			
			return wSabdTable;
			
		}
		
	}
	
	/**
	 * Log based band distribution class.
	 * 
	 * @author cwnl-kfudalew
	 */
	public static class LogBandDistribution implements BandDistribution {

		private double lso;
		private int    sso;
		
		/**
		 * Create a log band distribution instance supplying a sub-sonic offset and a log scale offset.
		 * The sub-sonic offset allows the first 'n' most bands to be combined into the first band group,
		 * while the remaining bands will follow the log distribution curve. The log scale offset refers
		 * to at what point in the log scale to use for distribution calculations. The lower the number, the
		 * few bands per group for the first few band groups.
		 * 
		 * @param pSubSonicOffset Groups the first 'n' bands into the sub-sonic band group. (default: 5)
		 * @param pLogScaleOffset Starting point on the log scale. (default: 20.0)
		 */
		public LogBandDistribution( int pSubSonicOffset, double pLogScaleOffset ) {
			sso = pSubSonicOffset;
			lso = pLogScaleOffset;
		}
		
		public int[] create( int pBandCount, int pFFTSampleSize ) {

			// -- We actually only use half of the available data because the higher bands are 
			//    not audible by humans.
			int wHss = (int)( pFFTSampleSize >> 1 ) - sso;
			
			double o = Math.log( lso );
			double r = (double)( pBandCount - 1 ) / ( Math.log( wHss + lso ) - o );
			
			int[] wSabdTable = new int[ pBandCount ];
			
			// -- Subsonic bands group.
			wSabdTable[ 0 ] = sso;
			
			// -- Divid reset of bands using log.
			for( int b = 0; b < wHss; b++ ) {
				
				double wCb = ( ( Math.log( (double)b + lso ) - o ) * r ) + 1.0;  

				wSabdTable[ (int)wCb ] = b + sso;

//				System.out.println( "BI :" + (int)wCb + " " + wCb + " " + wSabdTable[ (int)wCb ] );
				
			}

			return wSabdTable;
				
		}
		
	}

}
